package com.itcm.skymail.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itcm.common.utils.PageUtils;
import com.itcm.common.utils.Query;
import com.itcm.skymail.product.dao.CategoryDao;
import com.itcm.skymail.product.entity.CategoryEntity;
import com.itcm.skymail.product.service.CategoryBrandRelationService;
import com.itcm.skymail.product.service.CategoryService;
import com.itcm.skymail.product.vo.Catalog2Vo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );
        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        // 1. 先查出所有分类数据
        List<CategoryEntity> categories = baseMapper.selectList(null);
        // 2. 找出所有一级分类
        //    在映射到每个一级分类 添加它的子分类类
        return categories.stream()
                .filter(o -> o.getParentCid() == 0)
                // 给每个一级分类加子分类
                .peek(o -> o.setChildrens(getChildCategoryList(o, categories)))
                // 排序
                .sorted(Comparator.comparingInt(CategoryEntity::getSort))
                // 收集
                .collect(Collectors.toList());
    }

    @Override
    public void removeMenuByIds(List<Long> ids) {
        // TODO 判断是否被使用， 未使用则删除（逻辑删除）
        baseMapper.deleteBatchIds(ids);
    }

    @Override
    public List<Long> getCategoryPathById(Long catelogId) {
        List<Long> path = new ArrayList<>();
        List<Long> catelogPath = findCategoryPath(path, catelogId);
        Collections.reverse(catelogPath);
        return catelogPath;
    }

    // 分类数据更新的时候 触发删除缓存 指定缓存分区 再指定key  注意这里的key 接收的是一个spEL表达式，如果是普通字符串 需要里面加单引号
//    @Caching(evict = {
//            @CacheEvict(value = "category", key = "'getLevelOne'"),
//            @CacheEvict(value = "category", key = "'getCatalogJson'")
//    })
    // 删除缓存分区下所有缓存
    @CacheEvict(value = "category", allEntries = true)
    @Transactional
    @Override
    public void updateAll(CategoryEntity category) {
        this.baseMapper.updateById(category);
        if (!StringUtils.isEmpty(category.getName())) {
            categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
            // TODO 其他有冗余分类名的待更新
        }
    }

    @Cacheable(value = {"category"}, key = "#root.method.name")
    @Override
    public List<CategoryEntity> getLevelOne() {
        System.out.println("getLevelOne .....");
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("parent_cid", 0);
        return this.baseMapper.selectList(wrapper);
    }

    /**
     * 这里是循环嵌套的与数据库交互 是导致性能差的写法
     */
//    @Override
//    public Map<String, List<Catalog2Vo>> getCatalogJson() {
//        List<CategoryEntity> levelOne = getLevelOne();
//        Map<String, List<Catalog2Vo>> map = levelOne.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//            // 所有二级分类集合
//            List<CategoryEntity> entities = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
//            List<Catalog2Vo> catalog2Vos = null;
//            if (entities != null) {
//                catalog2Vos = entities.stream().map(item -> {
//                    List<CategoryEntity> categoryEntityList = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", item.getCatId()));
//                    Catalog2Vo catalog2Vo = null;
//                    if (categoryEntityList == null) {
//                        catalog2Vo = new Catalog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
//                    } else {
//                        List<Catalog2Vo.Catalog3Vo> catalog3Vos = categoryEntityList.stream().map(o -> {
//                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(item.getCatId().toString(), o.getCatId().toString(), o.getName());
//                            return catalog3Vo;
//                        }).collect(Collectors.toList());
//                        // 封装catalog2Vo
//                        catalog2Vo = new Catalog2Vo(v.getCatId().toString(), catalog3Vos, item.getCatId().toString(), item.getName());
//                    }
//                    return catalog2Vo;
//                }).collect(Collectors.toList());
//            }
//            return catalog2Vos;
//        }));
//        return map;
//    }

    /**
     * 使用 spring cache 改造
     */
    @Override
    @Cacheable(value = "category", key = "#root.methodName", sync = true)
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        List<CategoryEntity> categories = this.list(null);
        // 筛选出等级为一的
        List<CategoryEntity> levelOneCategories = categories.stream().filter(item -> item.getParentCid() == 0).collect(Collectors.toList());

        Map<String, List<Catalog2Vo>> map = levelOneCategories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//             所有二级分类集合
//            List<CategoryEntity> entities = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
            List<CategoryEntity> entities = categories.stream().filter(item -> item.getParentCid() == v.getCatId()).collect(Collectors.toList());
            List<Catalog2Vo> catalog2Vos = null;
            if (entities != null) {
                catalog2Vos = entities.stream().map(item -> {
//                    List<CategoryEntity> categoryEntityList = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", item.getCatId()));
                    List<CategoryEntity> categoryEntityList = categories.stream().filter(o -> o.getParentCid() == item.getCatId()).collect(Collectors.toList());
                    Catalog2Vo catalog2Vo = null;
                    if (categoryEntityList == null) {
                        catalog2Vo = new Catalog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
                    } else {
                        List<Catalog2Vo.Catalog3Vo> catalog3Vos = categoryEntityList.stream().map(o -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(item.getCatId().toString(), o.getCatId().toString(), o.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        // 封装catalog2Vo
                        catalog2Vo = new Catalog2Vo(v.getCatId().toString(), catalog3Vos, item.getCatId().toString(), item.getName());
                    }
                    return catalog2Vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));
        return map;
    }

    // 使用缓存
    public Map<String, List<Catalog2Vo>> getCatalogJson3() {
        /*
         *  1、空结果缓存：解决缓存穿透
         *  2、设置过期时间（加随机值）：解决缓存雪崩
         *  3、加锁：解决缓存击穿
         */
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (org.springframework.util.StringUtils.isEmpty(catalogJSON)) {
//            Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonWithRedisLock();
            return catalogJsonFromDb;
        }
        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
        return result;
    }

    public Map<String, List<Catalog2Vo>> getCatalogJsonWithRedisLock() {
        // 1. 占分布式锁
        String uuid = UUID.randomUUID().toString();
        // 设置锁和设置过期时间必须是原子性的 不能通过redis的两条命令设置，这里的命令等价于redis命令setnx ex
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
            log.info("获取分布式锁成功 ...");
            Map<String, List<Catalog2Vo>> dataFromDb;
            try {
                dataFromDb = getDataFromDb();
            } finally {
                // 删除锁 必须判断是当前锁 再删除，所以，为了保证原子性操作 需要采取redis+Lua脚本 完成
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                // 执行脚本
                redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
                        Collections.singletonList("lock"), uuid);
            }
            return dataFromDb;
        } else {
            // 加锁失败
            log.info("加锁失败，获取分布式锁 等待重试");

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatalogJsonWithRedisLock();
        }
    }


    /**
     * 使用redisson分布式锁
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonWithRedissonLock() {
        RLock lock = redisson.getLock("catalogJson-lock");
        lock.lock();
        Map<String, List<Catalog2Vo>> dataFromDb;
        try {
            dataFromDb = getDataFromDb();
        } finally {
            lock.unlock();
        }
        return dataFromDb;
    }

    private Map<String, List<Catalog2Vo>> getDataFromDb() {
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (!org.springframework.util.StringUtils.isEmpty(catalogJSON)) {
            Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2Vo>>>() {
            });
            return result;
        }
        System.out.println("查询了数据库");
        // 将上面的多次嵌套差数据库 变为一次 ，先一次查出所有
        List<CategoryEntity> categories = this.list(null);
        // 筛选出等级为一的
        List<CategoryEntity> levelOneCategories = categories.stream().filter(item -> item.getParentCid() == 0).collect(Collectors.toList());

        Map<String, List<Catalog2Vo>> map = levelOneCategories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//             所有二级分类集合
//            List<CategoryEntity> entities = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
            List<CategoryEntity> entities = categories.stream().filter(item -> item.getParentCid() == v.getCatId()).collect(Collectors.toList());
            List<Catalog2Vo> catalog2Vos = null;
            if (entities != null) {
                catalog2Vos = entities.stream().map(item -> {
//                    List<CategoryEntity> categoryEntityList = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", item.getCatId()));
                    List<CategoryEntity> categoryEntityList = categories.stream().filter(o -> o.getParentCid() == item.getCatId()).collect(Collectors.toList());
                    Catalog2Vo catalog2Vo = null;
                    if (categoryEntityList == null) {
                        catalog2Vo = new Catalog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
                    } else {
                        List<Catalog2Vo.Catalog3Vo> catalog3Vos = categoryEntityList.stream().map(o -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(item.getCatId().toString(), o.getCatId().toString(), o.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        // 封装catalog2Vo
                        catalog2Vo = new Catalog2Vo(v.getCatId().toString(), catalog3Vos, item.getCatId().toString(), item.getName());
                    }
                    return catalog2Vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));

        // 转换成json后 存入redis
        String s = JSON.toJSONString(map);
        redisTemplate.opsForValue().set("catalogJSON", s);
        return map;
    }


    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDb() {
        // 将上面的多次嵌套差数据库 变为一次 ，先一次查出所有
        List<CategoryEntity> categories = this.list(null);
        // 筛选出等级为一的
        List<CategoryEntity> levelOneCategories = categories.stream().filter(item -> item.getParentCid() == 0).collect(Collectors.toList());

        Map<String, List<Catalog2Vo>> map = levelOneCategories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//             所有二级分类集合
//            List<CategoryEntity> entities = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
            List<CategoryEntity> entities = categories.stream().filter(item -> item.getParentCid() == v.getCatId()).collect(Collectors.toList());
            List<Catalog2Vo> catalog2Vos = null;
            if (entities != null) {
                catalog2Vos = entities.stream().map(item -> {
//                    List<CategoryEntity> categoryEntityList = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", item.getCatId()));
                    List<CategoryEntity> categoryEntityList = categories.stream().filter(o -> o.getParentCid() == item.getCatId()).collect(Collectors.toList());
                    Catalog2Vo catalog2Vo = null;
                    if (categoryEntityList == null) {
                        catalog2Vo = new Catalog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
                    } else {
                        List<Catalog2Vo.Catalog3Vo> catalog3Vos = categoryEntityList.stream().map(o -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(item.getCatId().toString(), o.getCatId().toString(), o.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        // 封装catalog2Vo
                        catalog2Vo = new Catalog2Vo(v.getCatId().toString(), catalog3Vos, item.getCatId().toString(), item.getName());
                    }
                    return catalog2Vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));

        // 转换成json后 存入redis
        String s = JSON.toJSONString(map);
        redisTemplate.opsForValue().set("catalogJSON", s);
        return map;
    }

    private List<Long> findCategoryPath(List<Long> path, Long catelogId) {
        path.add(catelogId);
        CategoryEntity category = this.getById(catelogId);
        if (category.getParentCid() != 0) {
            findCategoryPath(path, category.getParentCid());
        }
        log.info("path: {}", path);
        return path;
    }

    // 根据当前分类 找出子类， 并通过递归找出子类的子类
    private List<CategoryEntity> getChildCategoryList(CategoryEntity currMenu, List<CategoryEntity> categories) {
        return categories.stream().filter(o -> o.getParentCid().equals(currMenu.getCatId()))
                .peek(o -> o.setChildrens(getChildCategoryList(o, categories)))
                .sorted(Comparator.comparingInt(CategoryEntity::getSort))
                .collect(Collectors.toList());
    }
}